﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using ClieOp.Record;

namespace ClieOp
{
    /// <summary>
    /// A transaction record.
    /// </summary>
    [Serializable]
    public class Post
    {
        #region PaymentType enum
        public enum PaymentType
        {
            DirectDebit,
            CreditorBusinessPayment,
            SalaryBusinessPayment
        }
        #endregion

        private static readonly CultureInfo Ci = CultureInfo.InvariantCulture;
        private readonly string _naambegunstigde = "";
        private readonly string _naambetaler = "";
        private readonly List<string> _omschrijvingen = new List<string>();
        private readonly string _transactiesoort;

        /// <summary>
        /// Create a new transaction direct debit record.
        /// </summary>
        /// <param name="bedragEur">The amount of the item in euros.</param>
        /// <param name="rekeningnummerbetaler">The account number of the payer of `bedragEur'.</param>
        /// <param name="naambetaler">The payer’s name according to the ordering party. Only the first 24
        /// characters of the field are processed, the rest is ignored.</param>
        /// <param name="betalingskenmerk">Reference assigned to the item by the ordering party.</param>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when [1] rekeningnummerbetaler is nine digits and is not
        /// a valid bank account number or [2] rekeningnummerbetaler is more than nine digits.</exception>
        public Post(decimal bedragEur, long rekeningnummerbetaler, string naambetaler, string betalingskenmerk)
        {
            PostPaymentType = PaymentType.DirectDebit;
            BedragEur = bedragEur;
            Rekeningnummerbetaler = rekeningnummerbetaler;
            if (Rekeningnummerbetaler.ToString(Ci).Length >= 9)
            {
                if (Utility.IsValidBankaccount(Rekeningnummerbetaler) != BankAccountCheckStatus.Valid)
                {
                    throw new ArgumentOutOfRangeException("rekeningnummerbetaler", string.Format(Ci, "Invalid account number {0}.", Rekeningnummerbetaler));
                }
            }
            else
            {
                _naambetaler = naambetaler;
            }
            _transactiesoort = string.IsNullOrEmpty(_naambetaler) ? "1001" : "1002";
            Betalingskenmerk = betalingskenmerk;
        }

        /// <summary>
        /// Create a new transaction business payment record.
        /// </summary>
        /// <param name="bedragEur">The amount of the item in euros.</param>
        /// <param name="rekeningnummerbegunstigde">The beneficiary's account number.</param>
        /// <param name="naambegunstigde">The beneficiary's name. (The 24 characters are used.)</param>
        /// <param name="betalingskenmerk">Reference assigned to the item by the ordering party.</param>
        /// <param name="paymentType">The type of payment. One of Post.PaymentType.CreditorBusinessPayment or
        /// Post.PaymentType.SalaryBusinessPayment.</param>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when [1] rekeningnummerbetaler is nine digits and is not
        /// a valid bank account number or [2] rekeningnummerbetaler is more than nine digits or [3] The paymentType is
        /// PaymentType.DirectDebit.</exception>
        public Post(decimal bedragEur, long rekeningnummerbegunstigde, string naambegunstigde, string betalingskenmerk, PaymentType paymentType)
        {
            if (paymentType == PaymentType.DirectDebit)
            {
                throw new ArgumentOutOfRangeException("paymentType", "Do not use this constructor for direct debit transactions.");
            }
            PostPaymentType = paymentType;
            BedragEur = bedragEur;
            Rekeningnummerbegunstigde = rekeningnummerbegunstigde;
            if (Rekeningnummerbegunstigde.ToString(Ci).Length >= 9)
            {
                if (Utility.IsValidBankaccount(Rekeningnummerbegunstigde) != BankAccountCheckStatus.Valid)
                {
                    throw new ArgumentOutOfRangeException("rekeningnummerbegunstigde",
                                                          string.Format(Ci, "Invalid account number {0}.", Rekeningnummerbegunstigde));
                }
            }
            else
            {
                _naambegunstigde = naambegunstigde;
            }
            _transactiesoort = PostPaymentType == PaymentType.CreditorBusinessPayment
                                   ? (string.IsNullOrEmpty(_naambegunstigde) ? "0005" : "0000")
                                   : (string.IsNullOrEmpty(_naambegunstigde) ? "0008" : "0003");
            Betalingskenmerk = betalingskenmerk;
        }

        /// <summary>
        /// The amount of the item in cents.
        /// </summary>
        internal long Bedrag
        {
            get { return (long)(Math.Round(BedragEur, 2) * 100); }
        }

        /// <summary>
        /// The amount of the item in euros.
        /// </summary>
        public decimal BedragEur { get; set; }

        /// <summary>
        /// Gets or sets the betalingskenmerk.
        /// </summary>
        public string Betalingskenmerk { get; private set; }

        /// <summary>
        /// Text explaining the transaction. Do not put your own name or that of the other account holder in the
        /// description -- banks will supply these automatically.
        /// </summary>
        public IList<string> Omschrijvingen
        {
            get { return _omschrijvingen; }
        }

        /// <summary>
        /// The type of payment.
        /// </summary>
        public PaymentType PostPaymentType { get; private set; }

        /// <summary>
        /// Account number beneficiary. (In the case of direct debit, your `own' account numer.)
        /// </summary>
        internal long Rekeningnummerbegunstigde { get; set; }

        /// <summary>
        /// The account number of the payer of `bedragEur'. (In the case of business payments, your `own' account numer.)
        /// </summary>
        internal long Rekeningnummerbetaler { get; set; }

        /// <summary>
        /// Boolean indicating if the transactiontype is checked or unchecked. Checked transactiontypes have a nine-digit
        /// bank account number, or are seven-digit numbers you have used before. (The latter is not implemented, seven-digit
        /// numbers are always indicated as `unchecked.')
        /// </summary>
        private bool TransactiesoortIsOnzuiver
        {
            get { return _transactiesoort == "0000" || _transactiesoort == "0003" || _transactiesoort == "1002"; }
        }

        /// <summary>
        /// Add a payment description to the transaction item.
        /// </summary>
        /// <param name="omschrijving">A description line as as string.</param>
        public void AddOmschrijving(string omschrijving)
        {
            if (_omschrijvingen.Count >= 4)
            {
                throw new ArgumentException("Cannot add `omschrijving'; maximum is 4.");
            }
            _omschrijvingen.Add(omschrijving);
        }

        /// <summary>
        /// Serialize the transaction item to a string. Conforms to the ClieOp specification.
        /// </summary>
        /// <returns>A string with the complete transaction item.</returns>
        public new string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(
                new Transactierecord
                    {
                        Transactiesoort = _transactiesoort,
                        Bedrag = Bedrag,
                        Rekeningnummerbetaler = Rekeningnummerbetaler,
                        Rekeningnummerbegunstigde = Rekeningnummerbegunstigde
                    }.ToString());
            if (TransactiesoortIsOnzuiver)
            {
                sb.Append(PostPaymentType == PaymentType.DirectDebit
                              ? new Naambetalerrecord {Naambetaler = _naambetaler}.ToString()
                              : new Naambegunstigderecord {Naambegunstigde = _naambegunstigde}.ToString());
            }
            sb.Append(new Betalingskenmerkrecord {Betalingskenmerk = Betalingskenmerk}.ToString());
            foreach (string oms in Omschrijvingen)
            {
                sb.Append(new Omschrijvingsrecord {Omschrijving = oms}.ToString());
            }
            return sb.ToString();
        }

        /// <summary>
        /// Parses a complete Post from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>A Post object.</returns>
        internal static Post Parse(ClieOpReader reader)
        {
            Post post = null;
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Transactierecord)
            {
                PaymentType paymentType = GetPaymentType((Transactierecord)reader.CurrentRecord);
                post = paymentType == PaymentType.DirectDebit ? ParseDirectDebitPost(reader) : ParseBusinessPaymentPost(reader, paymentType);
            }
            return post;
        }

        private static PaymentType GetPaymentType(Transactierecord transactierecord)
        {
            PaymentType paymentType;
            switch (transactierecord.Transactiesoort)
            {
                case "1002":
                case "1001":
                    paymentType = PaymentType.DirectDebit;
                    break;
                case "0000":
                case "0005":
                    paymentType = PaymentType.CreditorBusinessPayment;
                    break;
                case "0008":
                case "0003":
                    paymentType = PaymentType.SalaryBusinessPayment;
                    break;
                default:
                    throw new Exception(string.Format("Invalid `Transactiesoort': {0}.", transactierecord.Transactiesoort));
            }
            return paymentType;
        }

        private static Post ParseDirectDebitPost(ClieOpReader reader)
        {
            var transactieRecord = (Transactierecord)reader.CurrentRecord;
            decimal bedrag = transactieRecord.Bedrag / 100m;
            long rekeningnummerbetaler = transactieRecord.Rekeningnummerbetaler;
            long rekeningnummerbegunstigde = transactieRecord.Rekeningnummerbegunstigde;
            string naambetaler = "";
            string betalingskenmerk = "";
            reader.MoveToNext();
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Naambetalerrecord)
            {
                naambetaler = ((Naambetalerrecord)reader.CurrentRecord).Naambetaler.TrimEnd();
                reader.MoveToNext();
            }
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Woonplaatsrecord)
            {
                reader.MoveToNext();
            }
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Betalingskenmerkrecord)
            {
                var naambetalerRecord = (Betalingskenmerkrecord)reader.CurrentRecord;
                betalingskenmerk = naambetalerRecord.Betalingskenmerk.TrimEnd();
                reader.MoveToNext();
            }
            var post = new Post(bedrag, rekeningnummerbetaler, naambetaler, betalingskenmerk) {Rekeningnummerbegunstigde = rekeningnummerbegunstigde};
            while (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Omschrijvingsrecord)
            {
                post.AddOmschrijving(((Omschrijvingsrecord)reader.CurrentRecord).Omschrijving.TrimEnd());
                reader.MoveToNext();
            }
            return post;
        }

        private static Post ParseBusinessPaymentPost(ClieOpReader reader, PaymentType paymentType)
        {
            var transactieRecord = (Transactierecord)reader.CurrentRecord;
            decimal bedrag = transactieRecord.Bedrag / 100m;
            long rekeningnummerbetaler = transactieRecord.Rekeningnummerbetaler;
            long rekeningnummerbegunstigde = transactieRecord.Rekeningnummerbegunstigde;
            string naambegunstigde = "";
            string betalingskenmerk = "";
            reader.MoveToNext();
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Betalingskenmerkrecord)
            {
                betalingskenmerk = ((Betalingskenmerkrecord)reader.CurrentRecord).Betalingskenmerk.TrimEnd();
                reader.MoveToNext();
            }
            var omschrijvingen = new List<string>();
            while (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Omschrijvingsrecord)
            {
                omschrijvingen.Add(((Omschrijvingsrecord)reader.CurrentRecord).Omschrijving.TrimEnd());
                reader.MoveToNext();
            }
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Naambegunstigderecord)
            {
                naambegunstigde = ((Naambegunstigderecord)reader.CurrentRecord).Naambegunstigde.TrimEnd();
                reader.MoveToNext();
            }
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Woonplaatsrecord)
            {
                reader.MoveToNext();
            }
            var post = new Post(bedrag, rekeningnummerbegunstigde, naambegunstigde, betalingskenmerk, paymentType)
                           {Rekeningnummerbetaler = rekeningnummerbetaler};
            foreach (string omschrijving in omschrijvingen)
            {
                post.AddOmschrijving(omschrijving);
            }
            return post;
        }
    }
}